#include "Query.h"
void print(set<int>&s){
    for(auto &x:s)
        cout<<x<<" ";
    cout<<endl;
}


TextQuery::TextQuery(ifstream &fd)
    :file(new unordered_map<int, string>),
    words(new unordered_map<string, set<int>>)
{
    string line, word;
    size_t line_num = 1;
    while (getline(fd, line)) {
        (*file)[line_num] = line;
        istringstream sd(line);
        while (sd >> word) {
            int head = 0, tail = word.size() - 1;
            while (head<=tail && !isalpha(word[head])) {
                ++head;
            }
            while ( tail>=0 && !isalpha(word[tail])) {
                --tail;
            }
            if (head <= tail) {
                (*words)[string(word, head, tail - head + 1)].insert(line_num);
            }
        }
        ++line_num;
    }
}

QueryResult TextQuery::query(const string &word) const
{
    /* cout<<"TextQuery::query"<<endl; */
    shared_ptr<set<int>> ts(new set<int>((*words)[word]));
    return QueryResult(word, ts, file);
}

QueryResult::QueryResult(const string&qword,
                         const shared_ptr<set<int>> &lineNum,
                         shared_ptr<unordered_map<int, string>>file) 
    :qword(qword),
    lineNum(lineNum),
    file(file){}

ostream &print(ostream &os, const QueryResult &qr)
{
    for(auto &x: *qr.lineNum){
        os<<"(line "<<x<<") "
            <<(*qr.file)[x]<<endl;
    }
    return os;
}

ostream &operator<<(ostream&os, const QueryResult &qr){
    os<<"Executing Query for: "<<qr.qword<<endl
        <<qr.qword<<" occurs "<<qr.lineNum->size()
        <<" times"<<endl;
    return print(cout,qr);
}

WordQuery::WordQuery(const string &query_word) :query_word(query_word) {};
/* WordQuery::~WordQuery(){} */

QueryResult WordQuery::eval(const TextQuery &tq)
{
    /* cout<<"WordQuery::eval"<<endl; */
    return tq.query(query_word);
}

string WordQuery::rep()const
{
    return query_word;
}
ostream &operator<<(ostream &os, const Query &q){
    return os<<(q.rep());
}

Query::Query(shared_ptr<Query_base> pbase) :pbase(pbase) {}

Query::Query(string qWord) :pbase(new WordQuery(qWord)) {}

QueryResult Query::eval(const TextQuery &t)
{
    /* cout<<"Query::eval"<<endl; */
    return pbase->eval(t);
}

string Query::rep()const 
{
    /* cout<<"Query::rep"<<endl; */
    return pbase->rep();
}

Query operator~(const Query &q){
    return shared_ptr<Query_base>(new NotQuery(q));
}

NotQuery::NotQuery(const Query &q) :query(q){}

QueryResult NotQuery::eval(const TextQuery &t){
    QueryResult rsh = query.eval(t);
    /* print(*rsh.lineNum); */
    shared_ptr<set<int>> temp(new set<int>);
    for(auto &x: *(t.file)){
        if(rsh.lineNum->count(x.first) == 0){
            temp->insert(x.first);
        }
    }
    /* print(*rsh.lineNum); */
    return QueryResult(rep(),temp,t.file);
}

string NotQuery::rep() const{
    return "~("+query.rep()+")";
}

BinaryQuery::BinaryQuery(const Query &lsh, const Query &rsh, const string &opSym)
    :lsh(lsh), rsh(rsh), opSym(opSym){}

string BinaryQuery::rep()const{
    return "("+lsh.rep()+" "+opSym+" "+rsh.rep()+")";
}
    
OrQuery::OrQuery(const Query &lsh, const Query &rsh, const string &opSym)
    :BinaryQuery(lsh, rsh, opSym){}

QueryResult OrQuery::eval(const TextQuery &t) {
    shared_ptr<set<int>>temp(new set<int>);
    auto l_res = lsh.eval(t);
    auto r_res = rsh.eval(t);
    set_union(l_res.lineNum->begin(),
              l_res.lineNum->end(),
              r_res.lineNum->begin(),
              r_res.lineNum->end(),
              inserter(*temp, temp->begin()));  
    return QueryResult(rep(), temp, t.file);
}

Query operator|(const Query &lsh, const Query &rsh){
    return shared_ptr<Query_base>(new OrQuery(lsh, rsh, "|"));
}

AndQuery::AndQuery(const Query &lsh, const Query &rsh, const string &opSym)
    :BinaryQuery(lsh, rsh, opSym){}

QueryResult AndQuery::eval(const TextQuery &t) {
    shared_ptr<set<int>>temp(new set<int>);
    auto l_res = lsh.eval(t);
    auto r_res = rsh.eval(t);
    set_intersection(l_res.lineNum->begin(),
              l_res.lineNum->end(),
              r_res.lineNum->begin(),
              r_res.lineNum->end(),
              inserter(*temp, temp->begin()));  
    return QueryResult(rep(), temp, t.file);
}

Query operator&(const Query &lsh, const Query &rsh){
    return shared_ptr<Query_base>(new AndQuery(lsh, rsh, "&"));
}












